package gs

// Package progress provides io.Reader and io.Writer with progress and remaining time estimation.
//
//	ctx := context.Background()
//
//	// get a reader and the total expected number of bytes
//	s := `Now that's what I call progress`
//	size := len(s)
//	r := progress.NewReader(strings.NewReader(s))
//
//	// Start a goroutine printing progress
//	go func() {
//	    ctx := context.Background()
//	    progressChan := progress.NewTicker(ctx, r, size, 1*time.Second)
//	    for p := range progressChan {
//	    	fmt.Printf("\r%v remaining...", p.Remaining().Round(time.Second))
//	    }
//	    fmt.Println("\rdownload is completed")
//	}()
//
//	// use the Reader as normal
//	if _, err := io.Copy(dest, r); err != nil {
//		log.Fatalln(err)
//	}

import (
	"context"
	"io"
	"sync"
	"time"

	"golang.org/x/term"
)

var (
	DEFAULT_PROGRESS_OPT = &ProgressOption{
		ShowLine:   1,
		Size:       0,
		RefreshSec: 3,
		Msg:        "Progress doing",
		ShowCall: func(percent float64, opt *ProgressOption, completed bool) {
			if completed {
				Str("").ANSISave().ANSIHideCursor().ANSICursor(opt.ShowLine, 1).ANSIClearThisLine().Add(Str("[completed]" + opt.Msg).Color("g")).ANSIShowCursor().ANSIRestore().Print()
				return
			}
			_, width := GetWindowsSize()
			ss := Str("%% %.2f | %s ").F(percent, opt.Msg)
			left := width - len(ss)
			l := width * int(percent) / 100
			ss += Str(" ").Repeat(left)
			ss = ss.ANSIGreen()
			// fmt.Println(ss[:l].ANSISelected()+ss[l:], l)

			sspre := ss[:l].ANSISelected()
			ssend := ss[l:]
			Str("").ANSISave().ANSIHideCursor().ANSICursor(opt.ShowLine, 1).ANSIClearThisLine().Add(sspre + ssend).ANSIShowCursor().ANSIRestore().Print()
		},
	}
)

type ProgressOption struct {
	IsRun      bool
	ShowLine   int
	Size       int64
	RefreshSec int
	Msg        string
	ShowCall   func(percent float64, opt *ProgressOption, completed bool)
}

// Reader counts the bytes read through it.
type ProgressReader struct {
	r io.Reader

	lock sync.RWMutex // protects n and err
	n    int64
	err  error
}

// NewReader makes a new Reader that counts the bytes
// read through it.
func NewProgressReader(r io.Reader) *ProgressReader {
	return &ProgressReader{
		r: r,
	}
}

func (r *ProgressReader) Read(p []byte) (n int, err error) {
	n, err = r.r.Read(p)
	r.lock.Lock()
	r.n += int64(n)
	r.err = err
	r.lock.Unlock()
	return
}

// N gets the number of bytes that have been read
// so far.
func (r *ProgressReader) N() int64 {
	var n int64
	r.lock.RLock()
	n = r.n
	r.lock.RUnlock()
	return n
}

// Err gets the last error from the Reader.
func (r *ProgressReader) Err() error {
	var err error
	r.lock.RLock()
	err = r.err
	r.lock.RUnlock()
	return err
}

// Writer counts the bytes written through it.
type ProgressWriter struct {
	w io.Writer

	lock sync.RWMutex // protects n and err
	n    int64
	err  error
}

// NewWriter gets a Writer that counts the number
// of bytes written.
func NewProgressWriter(w io.Writer) *ProgressWriter {
	return &ProgressWriter{
		w: w,
	}
}

func (w *ProgressWriter) Write(p []byte) (n int, err error) {
	n, err = w.w.Write(p)
	w.lock.Lock()
	w.n += int64(n)
	w.err = err
	w.lock.Unlock()
	return
}

// N gets the number of bytes that have been written
// so far.
func (w *ProgressWriter) N() int64 {
	var n int64
	w.lock.RLock()
	n = w.n
	w.lock.RUnlock()
	return n
}

// Err gets the last error from the Writer.
func (w *ProgressWriter) Err() error {
	var err error
	w.lock.RLock()
	err = w.err
	w.lock.RUnlock()
	return err
}

// Counter counts bytes.
// Both Reader and Writer are Counter types.
type Counter interface {
	// N gets the current count value.
	// For readers and writers, this is the number of bytes
	// read or written.
	// For other contexts, the number may be anything.
	N() int64
	// Err gets the last error from the Reader or Writer.
	// When the process is finished, this will be io.EOF.
	Err() error
}

// Progress represents a moment of progress.
type Progress struct {
	n         float64
	size      float64
	estimated time.Time
	err       error
}

// N gets the total number of bytes read or written
// so far.
func (p Progress) N() int64 {
	return int64(p.n)
}

// Size gets the total number of bytes that are expected to
// be read or written.
func (p Progress) Size() int64 {
	return int64(p.size)
}

// Complete gets whether the operation is complete or not.
// Always returns false if the Size is unknown (-1).
func (p Progress) Complete() bool {
	if p.err == io.EOF {
		return true
	}
	if p.size == -1 {
		return false
	}
	return p.n >= p.size
}

// Percent calculates the percentage complete.
func (p Progress) Percent() float64 {
	if p.n == 0 {
		return 0
	}
	if p.n >= p.size {
		return 100
	}
	return 100.0 / (p.size / p.n)
}

// Remaining gets the amount of time until the operation is
// expected to be finished. Use Estimated to get a fixed completion time.
// Returns -1 if no estimate is available.
func (p Progress) Remaining() time.Duration {
	if p.estimated.IsZero() {
		return -1
	}
	return time.Until(p.estimated)
}

// Estimated gets the time at which the operation is expected
// to finish. Use Remaining to get a Duration.
// Estimated().IsZero() is true if no estimate is available.
func (p Progress) Estimated() time.Time {
	return p.estimated
}

// NewTicker gets a channel on which ticks of Progress are sent
// at duration d intervals until the operation is complete at which point
// the channel is closed.
// The counter is either a Reader or Writer (or any type that can report its progress).
// The size is the total number of expected bytes being read or written.
// If the context cancels the operation, the channel is closed.
func NewProgressTicker(ctx context.Context, counter Counter, size int64, d time.Duration) <-chan Progress {
	var (
		started = time.Now()
		ch      = make(chan Progress)
	)
	go func() {
		defer close(ch)
		for {
			select {
			case <-ctx.Done():
				// context has finished - exit
				return
			case <-time.After(d):
				progress := Progress{
					n:    float64(counter.N()),
					size: float64(size),
					err:  counter.Err(),
				}
				ratio := progress.n / progress.size
				past := float64(time.Since(started))
				if progress.n > 0.0 {
					total := time.Duration(past / ratio)
					if total < 168*time.Hour {
						// don't send estimates that are beyond a week
						progress.estimated = started.Add(total)
					}
				}
				ch <- progress
				if progress.Complete() {
					return
				}
			}
		}
	}()
	return ch
}

func (popt *ProgressOption) Backup() *ProgressOption {
	return &ProgressOption{
		ShowLine:   popt.ShowLine,
		Size:       popt.Size,
		RefreshSec: popt.RefreshSec,
		Msg:        popt.Msg,
		ShowCall:   popt.ShowCall,
		IsRun:      popt.IsRun,
	}
}

func (popt *ProgressOption) SetMsg(msg Str) *ProgressOption {
	e := popt.Backup()
	e.Msg = msg.Str()
	return e
}

func (popt *ProgressOption) SetSize(i int64) *ProgressOption {
	e := popt.Backup()
	e.Size = i
	return e
}

func (popt *ProgressOption) SetShowCall(call func(percent float64, opt *ProgressOption, completed bool)) {
	popt.ShowCall = call
}

func (popt *ProgressOption) NormalStart(size int64, refreshTimeSec int, update func(now int64) (updatedNow int64)) (opt *ProgressOption) {
	opt = popt.Backup()
	opt.IsRun = true
	go func() {
		// ctx := context.Background()
		// _, width := GetWindowsSize()
		// width -= 10
		// cs := NewProgressTicker(ctx, progressR, allsize, time.Duration(popt.RefreshSec)*time.Second)
		var now int64 = 0

		for now < size {
			time.Sleep(time.Duration(refreshTimeSec) * time.Second)
			now = update(now)
			percent := float64(now) * float64(100) / float64(size)
			opt.ShowCall(percent, opt, false)
		}
		opt.ShowCall(100.00, opt, true)
		opt.IsRun = false
	}()
	time.Sleep(1 * time.Second)
	return
}
func (opt *ProgressOption) Wait() {
	for opt.IsRun {
		time.Sleep(500 * time.Millisecond)
	}

}

func (popt *ProgressOption) Copy(w io.Writer, r io.Reader) (wrriten int64, err error) {
	allsize := popt.Size
	ctx := context.Background()
	progressR := NewProgressReader(r)
	popt.IsRun = true
	go func() {
		// ctx := context.Background()
		// _, width := GetWindowsSize()
		// width -= 10
		cs := NewProgressTicker(ctx, progressR, allsize, time.Duration(popt.RefreshSec)*time.Second)
		for p := range cs {
			popt.ShowCall(p.Percent(), popt, false)
		}
		popt.ShowCall(100.00, popt, true)
		popt.IsRun = false
	}()
	time.Sleep(10 * time.Millisecond)
	return io.Copy(w, progressR)
}

func GetWindowsSize() (row int, col int) {
	col, row, err := term.GetSize(0)
	if err != nil {
		panic(err)
	}
	return
}
